package com.gmrz.uas.plugin.cert;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.cms.*;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.Store;

import javax.security.auth.x500.X500Principal;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.*;

/**
 * Created by gmrz-ops on 2019/9/11.
 */
public class CAUtil {

//    public static KeyPair getRSAKey() throws Exception{
//        KeyPairGenerator ecPair = KeyPairGenerator.getInstance("RSA", "BC");
//        ecPair.initialize(2048, new SecureRandom()); // keyPair密钥对KeyPair keyPair2 = ecPair.generateKeyPair();
//        KeyPair keyPair = ecPair.generateKeyPair();
//        return keyPair;
//    }

    public static KeyPair getRSAKey() throws Exception{
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", "BC");
        RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(2048,RSAKeyGenParameterSpec.F4);
        keyPairGen.initialize(spec);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        return keyPair;
    }

    public static PKCS10CertificationRequest createRSAP10(String dn, KeyPair keyPair) {
        try {
            PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(
                    new X500Principal(dn), keyPair.getPublic());
            JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA");
            ContentSigner signer = csBuilder.build(keyPair.getPrivate());
            PKCS10CertificationRequest csr = p10Builder.build(signer);
            return csr;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static X509Certificate signCert(PKCS10CertificationRequest p10CSR, X509Certificate caRootCert, PrivateKey caRootPrivKey){
        InputStream is1 = null;
        try {
            String signatureAlgorithm = "SHA256WithRSA";
            ContentSigner sigGen = new JcaContentSignerBuilder(signatureAlgorithm).build(caRootPrivKey);;
            SubjectPublicKeyInfo p10PkInfo = p10CSR.getSubjectPublicKeyInfo();

            SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(p10PkInfo.getEncoded());

            X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder(
                    new X500Name(caRootCert.getIssuerDN().getName()),
                    new BigInteger("1"), new Date(System.currentTimeMillis()),
                    new Date(System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60 * 1000), p10CSR.getSubject(), publicKeyInfo);

            X509CertificateHolder holder = myCertificateGenerator.build(sigGen);
            org.bouncycastle.asn1.x509.Certificate eeX509CertificateStructure = holder.toASN1Structure();

            CertificateFactory cf = new CertificateFactory();
            // Read Certificate
            is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
            X509Certificate theCert = (X509Certificate) cf.engineGenerateCertificate(is1);
            is1.close();
            return theCert;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(is1 != null){
                try {
                    is1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes = DatatypeConverter.parseHexBinary(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    public static byte[] createRootCert(String caKeyStorePath,KeyPair keyPair) throws Exception {

        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        Date startDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
        Date expiryDate = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000);

        X500NameBuilder casubjectBuilder = new X500NameBuilder(BCStyle.INSTANCE);
        casubjectBuilder.addRDN(BCStyle.CN, "GMRZ");
        casubjectBuilder.addRDN(BCStyle.OU, "GMRZ ROOT OU");
        casubjectBuilder.addRDN(BCStyle.UNIQUE_IDENTIFIER, "GMRZ ROOT IDENTIFIER");
        X500Name subject = casubjectBuilder.build();

        String signatureAlgorithm = "SHA256WithRSA"; // <-- Use appropriate signature algorithm based on your keyPair algorithm.

        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);

        JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(subject, new BigInteger("1",10), startDate, expiryDate, subject, keyPair.getPublic());

        X509CertificateHolder holder = certBuilder.build(contentSigner);

        X509Certificate cert = new JcaX509CertificateConverter().getCertificate(holder);

        //saveCa(caKeyStorePath+".key",privateKey.getEncoded(),caKeyStorePath,cert);
        System.out.println("rootCert:"+ DatatypeConverter.printHexBinary(cert.getEncoded()));
        System.out.println("rootPrivateKey:"+DatatypeConverter.printHexBinary(privateKey.getEncoded()));
        System.out.println("rootPublicKey:"+DatatypeConverter.printHexBinary(publicKey.getEncoded()));
        return cert.getEncoded();
    }

    public static X509Certificate getCertFromByte(byte[] certByte) throws Exception {
        X509Certificate cert = null;
        java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
        if ((certByte != null) && (certByte.length > 0)) {
            cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certByte));
        }
        return cert;
    }

    public static  byte[] SHA256_DIGEST_INFO = {
            0x30,0x31,0x30,0x0d,0x06,0x09,0x60,(byte) 0x86,
            0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,
            0x00,0x04,0x20};
    public static boolean verifyRSAP7(byte[] p7Data) {
        try {
            CMSSignedData sig = new CMSSignedData(p7Data);
            Store certs = sig.getCertificates();
            SignerInformationStore signers = sig.getSignerInfos();
            Collection<?> c = signers.getSigners();
            Iterator<?> it = c.iterator();
            SignerInformation signer;
            Collection<?> certCollection;
            Iterator<?> certIt;
            CMSTypedData contentInfo = (CMSTypedData)sig.getSignedContent();
            if (c.size() != 1) {
                throw new Exception();
            }
            while (it.hasNext()) {
                signer = (SignerInformation) it.next();
                certCollection = certs.getMatches(signer.getSID());
                certIt = certCollection.iterator();
                X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
                ASN1Sequence seq = (ASN1Sequence)(certHolder.getSubjectPublicKeyInfo().getPublicKey().toASN1Primitive());
                ASN1Encodable[] ss = seq.toArray();
                ASN1Integer a1 = (ASN1Integer) ss[0];
                ASN1Integer a2 = (ASN1Integer) ss[1];

                byte[] bts = certHolder.getSubjectPublicKeyInfo().getPublicKey().getEncoded();
                KeyStore keyStore = KeyStore.getInstance("JKS");

                KeyFactory keyFactory = KeyFactory.getInstance("RSA");

                PublicKey publicKey = keyFactory.generatePublic(new RSAPublicKeySpec(a1.getValue(), a2.getValue()));

                byte[] signature = signer.getSignature();
                //System.out.print("signature:" + UtilByte.byte2hex(signature) + "\n");

                byte[] data = (byte[]) contentInfo.getContent();
                //System.out.print("data:" + UtilByte.byte2hex(data) + "\n");

                MessageDigest messageDigest;
                messageDigest = MessageDigest.getInstance("SHA-256");
                messageDigest.update(data);
                byte[] signedData =  messageDigest.digest();
                byte[] oDATA = new byte[51];

                System.arraycopy(SHA256_DIGEST_INFO, (short)0, oDATA, (short)(0), (short)SHA256_DIGEST_INFO.length);
                System.arraycopy(signedData, (short)0, oDATA, (short)(SHA256_DIGEST_INFO.length), (short)signedData.length);

                Signature sig111 = Signature.getInstance("RSA","BC");
                sig111.initVerify(publicKey);
                sig111.update(oDATA);
                return sig111.verify(signature);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
        return false;
    }

    public static byte[] generateSignatureBlock(X509Certificate signerCert ,PrivateKey privateKey, byte[] signatureFileBytes)
            throws InvalidKeyException, CertificateEncodingException, SignatureException, OperatorCreationException {
        List<X509Certificate> certificates = new ArrayList<X509Certificate>();
        certificates.add(signerCert);
        JcaCertStore certs = new JcaCertStore(certificates);

        String jcaSignatureAlgorithm = "SHA256withRSA";
        try {
            ContentSigner signer = new JcaContentSignerBuilder(jcaSignatureAlgorithm).build(privateKey);
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            gen.addSignerInfoGenerator(
                    new SignerInfoGeneratorBuilder(
                            new JcaDigestCalculatorProviderBuilder().build(),
                            SignerInfoSignatureAlgorithmFinder.INSTANCE)
                            .setDirectSignature(true)
                            .build(signer, new JcaX509CertificateHolder(signerCert)));
            gen.addCertificates(certs);

            CMSSignedData sigData =
                    gen.generate(new CMSProcessableByteArray(signatureFileBytes), true);

            ByteArrayOutputStream out = new ByteArrayOutputStream();

                ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
                DEROutputStream dos = new DEROutputStream(out);
                dos.writeObject(asn1.readObject());

            return out.toByteArray();
        } catch (Exception e) {
            throw new SignatureException("Failed to generate signature", e);
        }
    }

    private static class SignerInfoSignatureAlgorithmFinder
            implements CMSSignatureEncryptionAlgorithmFinder {
        private static final SignerInfoSignatureAlgorithmFinder INSTANCE =
                new SignerInfoSignatureAlgorithmFinder();

        private static final AlgorithmIdentifier DSA =
                new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, DERNull.INSTANCE);

        private final CMSSignatureEncryptionAlgorithmFinder mDefault =
                new DefaultCMSSignatureEncryptionAlgorithmFinder();

        @Override
        public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier id) {
            // Use the default chooser, but replace dsaWithSha1 with dsa. This is because "dsa" is
            // accepted by any Android platform whereas "dsaWithSha1" is accepted only since
            // API Level 9.
            id = mDefault.findEncryptionAlgorithm(id);
            if (id != null) {
                ASN1ObjectIdentifier oid = id.getAlgorithm();
                if (X9ObjectIdentifiers.id_dsa_with_sha1.equals(oid)) {
                    return DSA;
                }
            }

            return id;
        }
    }


    public static void main(String[] args) throws Exception{
        Security.addProvider(new BouncyCastleProvider());
        KeyPair caKeyPair = getRSAKey();
        byte[] rootCertByte = createRootCert("",caKeyPair);

        String dn = "CN=0305@111222199909103519@username@654,OU=Individual-1,OU=CMBC_MSP,O=CMBC,C=CN";
        KeyPair userKeyPair = getRSAKey();
        PKCS10CertificationRequest pkcs10CertificationRequest = createRSAP10(dn, userKeyPair);


        X509Certificate caRootCert = getCertFromByte(rootCertByte);
        X509Certificate cert = signCert(pkcs10CertificationRequest, caRootCert, caKeyPair.getPrivate());
        byte[] p7data = generateSignatureBlock(cert ,userKeyPair.getPrivate(), "test context".getBytes());
        boolean f = verifyRSAP7(p7data);
        System.out.println(f);
    }

}
